/* *******************************************************************
 * Copyright (c) 2004 IBM Corporation.
 * All rights reserved.
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Public License v 2.0
 * which accompanies this distribution and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
 *
 * ******************************************************************/
package org.aspectj.weaver.tools;

import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

import org.aspectj.bridge.AbortException;
import org.aspectj.bridge.IMessage;
import org.aspectj.bridge.IMessage.Kind;
import org.aspectj.bridge.IMessageHandler;
import org.aspectj.weaver.patterns.PatternParser;
import org.aspectj.weaver.patterns.Pointcut;
import org.aspectj.weaver.patterns.PointcutRewriter;

import org.junit.Assert;
import junit.framework.TestCase;

/**
 * Test cases for the PointcutParser class
 */
public class PointcutParserTest extends TestCase {

	private boolean needToSkip = false;

	/** this condition can occur on the build machine only, and is way too complex to fix right now... */
	private boolean needToSkipPointcutParserTests() {
		try {
			Class.forName("org.aspectj.weaver.reflect.Java15ReflectionBasedReferenceTypeDelegate", false, this.getClass()
					.getClassLoader());// ReflectionBasedReferenceTypeDelegate.class.getClassLoader());
		} catch (ClassNotFoundException cnfEx) {
			return true;
		}
		return false;
	}

	protected void setUp() throws Exception {
		super.setUp();
		needToSkip = needToSkipPointcutParserTests();
	}

	public void testGetAllSupportedPointcutPrimitives() {
		if (needToSkip) {
			return;
		}

		Set<PointcutPrimitive> s = PointcutParser.getAllSupportedPointcutPrimitives();
		assertEquals("Should be 21 elements in the set", 21, s.size());
		assertFalse("Should not contain if pcd", s.contains(PointcutPrimitive.IF));
		assertFalse("Should not contain cflow pcd", s.contains(PointcutPrimitive.CFLOW));
		assertFalse("Should not contain cflowbelow pcd", s.contains(PointcutPrimitive.CFLOW_BELOW));
	}

	public void testEmptyConstructor() {
		if (needToSkip) {
			return;
		}

		PointcutParser parser = PointcutParser
				.getPointcutParserSupportingAllPrimitivesAndUsingSpecifiedClassloaderForResolution(this.getClass().getClassLoader());
		Set<PointcutPrimitive> s = parser.getSupportedPrimitives();
		assertEquals("Should be 21 elements in the set", 21, s.size());
		assertFalse("Should not contain if pcd", s.contains(PointcutPrimitive.IF));
		assertFalse("Should not contain cflow pcd", s.contains(PointcutPrimitive.CFLOW));
		assertFalse("Should not contain cflowbelow pcd", s.contains(PointcutPrimitive.CFLOW_BELOW));
	}

	public void testSetConstructor() {
		if (needToSkip) {
			return;
		}

		Set<PointcutPrimitive> p = PointcutParser.getAllSupportedPointcutPrimitives();
		PointcutParser parser = PointcutParser
				.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(p, this.getClass()
						.getClassLoader());
		assertEquals("Should use the set we pass in", p, parser.getSupportedPrimitives());
		Set<PointcutPrimitive> q = new HashSet<>();
		q.add(PointcutPrimitive.ARGS);
		parser = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(q, this
				.getClass().getClassLoader());
		assertEquals("Should have only one element in set", 1, parser.getSupportedPrimitives().size());
		assertEquals("Should only have ARGS pcd", PointcutPrimitive.ARGS, parser.getSupportedPrimitives().iterator().next());
	}

	public void testParsePointcutExpression() {
		if (needToSkip) {
			return;
		}

		PointcutParser p = PointcutParser.getPointcutParserSupportingAllPrimitivesAndUsingSpecifiedClassloaderForResolution(this
				.getClass().getClassLoader());
		IMessageHandler current = p.setCustomMessageHandler(new IgnoreWarningsMessageHandler());
		try {
			p.parsePointcutExpression("(adviceexecution() || execution(* *.*(..)) || handler(Exception) || "
					+ "call(Foo Bar+.*(Goo)) || get(* foo) || set(Foo+ (Goo||Moo).s*) || "
					+ "initialization(Foo.new(..)) || preinitialization(*.new(Foo,..)) || "
					+ "staticinitialization(org.xzy.abc..*)) && (this(Foo) || target(Boo) ||" + "args(A,B,C)) && !handler(X)");
		} finally {
			p.setCustomMessageHandler(current);
		}
		try {
			p.parsePointcutExpression("gobble-de-gook()");
			fail("Expected IllegalArgumentException");
		} catch (IllegalArgumentException ex) {
		}
	}

	public void testParseExceptionErrorMessages() {
		if (needToSkip) {
			return;
		}

		PointcutParser p = PointcutParser.getPointcutParserSupportingAllPrimitivesAndUsingSpecifiedClassloaderForResolution(this
				.getClass().getClassLoader());
		try {
			p.parsePointcutExpression("execution(int Foo.*(..) && args(Double)");
			fail("Expected IllegalArgumentException");
		} catch (IllegalArgumentException ex) {
			assertTrue("Pointcut is not well-formed message",
					ex.getMessage().startsWith("Pointcut is not well-formed: expecting ')' at character position 24"));
		}
	}

	public void testOperatorPrecedence_319190() throws Exception {
		if (needToSkip) {
			return;
		}

		String s = null;
		Pointcut p = null;

		s = "(execution(* A.credit(float)) || execution(* A.debit(float))) && this(acc) && args(am)  || execution(* C.*(Account, float)) && args(acc, am)";
		p = new PatternParser(s).parsePointcut();
		Assert.assertEquals(
				"(((execution(* A.credit(float)) || execution(* A.debit(float))) && (this(acc) && args(am))) || (execution(* C.*(Account, float)) && args(acc, am)))",
				p.toString());

		s = "(if(true) || if(false)) && this(acc) && args(am)  || if(true) && args(acc, am)";
		p = new PatternParser(s).parsePointcut();
		// bugged was: ((if(true) || if(false)) && (this(acc) && (args(am) || (if(true) && args(acc, am)))))
		Assert.assertEquals("(((if(true) || if(false)) && (this(acc) && args(am))) || (if(true) && args(acc, am)))", p.toString());
		p = new PointcutRewriter().rewrite(p);
		Assert.assertEquals("(((this(acc) && args(am)) && if(true)) || (args(acc, am) && if(true)))", p.toString());

		s = "if(true) && if(false) || if(true)";
		p = new PatternParser(s).parsePointcut();
		assertEquals("((if(true) && if(false)) || if(true))", p.toString());
		p = new PointcutRewriter().rewrite(p);
		Assert.assertEquals("if(true)", p.toString());
	}

	public void testParseIfPCD() {
		if (needToSkip) {
			return;
		}

		PointcutParser p = PointcutParser.getPointcutParserSupportingAllPrimitivesAndUsingSpecifiedClassloaderForResolution(this
				.getClass().getClassLoader());
		try {
			p.parsePointcutExpression("if(true)");
			fail("Expected UnsupportedPointcutPrimitiveException");
		} catch (UnsupportedPointcutPrimitiveException ex) {
			assertEquals("Should not support IF", PointcutPrimitive.IF, ex.getUnsupportedPrimitive());
		}
	}

	public void testParseCflowPCDs() {
		if (needToSkip) {
			return;
		}

		PointcutParser p = PointcutParser.getPointcutParserSupportingAllPrimitivesAndUsingSpecifiedClassloaderForResolution(this
				.getClass().getClassLoader());
		try {
			p.parsePointcutExpression("cflow(this(t))");
			fail("Expected UnsupportedPointcutPrimitiveException");
		} catch (UnsupportedPointcutPrimitiveException ex) {
			assertEquals("Should not support CFLOW", PointcutPrimitive.CFLOW, ex.getUnsupportedPrimitive());
		}
		try {
			p.parsePointcutExpression("cflowbelow(this(t))");
			fail("Expected UnsupportedPointcutPrimitiveException");
		} catch (UnsupportedPointcutPrimitiveException ex) {
			assertEquals("Should not support CFLOW_BELOW", PointcutPrimitive.CFLOW_BELOW, ex.getUnsupportedPrimitive());
		}
	}

	public void testParseReferencePCDs() {
		if (needToSkip) {
			return;
		}

		Set<PointcutPrimitive> pcKinds = PointcutParser.getAllSupportedPointcutPrimitives();
		pcKinds.remove(PointcutPrimitive.REFERENCE);
		PointcutParser p = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(
				pcKinds, this.getClass().getClassLoader());
		try {
			p.parsePointcutExpression("bananas(String)");
			fail("Expected UnsupportedPointcutPrimitiveException");
		} catch (UnsupportedPointcutPrimitiveException ex) {
			assertTrue(ex.getUnsupportedPrimitive() == PointcutPrimitive.REFERENCE);
		}
	}

	public void testParseUnsupportedPCDs() {
		if (needToSkip) {
			return;
		}

		Set s = new HashSet();
		PointcutParser p = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(
				s, this.getClass().getClassLoader());
		try {
			p.parsePointcutExpression("args(x)");
			fail("Expected UnsupportedPointcutPrimitiveException");
		} catch (UnsupportedPointcutPrimitiveException ex) {
			assertEquals("Args", PointcutPrimitive.ARGS, ex.getUnsupportedPrimitive());
		}
		try {
			p.parsePointcutExpression("within(x)");
			fail("Expected UnsupportedPointcutPrimitiveException");
		} catch (UnsupportedPointcutPrimitiveException ex) {
			assertEquals("Within", PointcutPrimitive.WITHIN, ex.getUnsupportedPrimitive());
		}
		try {
			p.parsePointcutExpression("withincode(new(..))");
			fail("Expected UnsupportedPointcutPrimitiveException");
		} catch (UnsupportedPointcutPrimitiveException ex) {
			assertEquals("Withincode", PointcutPrimitive.WITHIN_CODE, ex.getUnsupportedPrimitive());
		}
		try {
			p.parsePointcutExpression("handler(Exception)");
			fail("Expected UnsupportedPointcutPrimitiveException");
		} catch (UnsupportedPointcutPrimitiveException ex) {
			assertEquals("handler", PointcutPrimitive.HANDLER, ex.getUnsupportedPrimitive());
		}
		try {
			p.parsePointcutExpression("this(X)");
			fail("Expected UnsupportedPointcutPrimitiveException");
		} catch (UnsupportedPointcutPrimitiveException ex) {
			assertEquals("this", PointcutPrimitive.THIS, ex.getUnsupportedPrimitive());
		}
		try {
			p.parsePointcutExpression("target(X)");
			fail("Expected UnsupportedPointcutPrimitiveException");
		} catch (UnsupportedPointcutPrimitiveException ex) {
			assertEquals("target", PointcutPrimitive.TARGET, ex.getUnsupportedPrimitive());
		}
		try {
			p.parsePointcutExpression("this(X) && target(Y)");
			fail("Expected UnsupportedPointcutPrimitiveException");
		} catch (UnsupportedPointcutPrimitiveException ex) {
			assertEquals("This", PointcutPrimitive.THIS, ex.getUnsupportedPrimitive());
		}
		try {
			p.parsePointcutExpression("this(X) || target(Y)");
			fail("Expected UnsupportedPointcutPrimitiveException");
		} catch (UnsupportedPointcutPrimitiveException ex) {
			assertEquals("This", PointcutPrimitive.THIS, ex.getUnsupportedPrimitive());
		}
		try {
			p.parsePointcutExpression("!this(X)");
			fail("Expected UnsupportedPointcutPrimitiveException");
		} catch (UnsupportedPointcutPrimitiveException ex) {
			assertEquals("This", PointcutPrimitive.THIS, ex.getUnsupportedPrimitive());
		}
		try {
			p.parsePointcutExpression("call(* *.*(..))");
			fail("Expected UnsupportedPointcutPrimitiveException");
		} catch (UnsupportedPointcutPrimitiveException ex) {
			assertEquals("Call", PointcutPrimitive.CALL, ex.getUnsupportedPrimitive());
		}
		try {
			p.parsePointcutExpression("execution(* *.*(..))");
			fail("Expected UnsupportedPointcutPrimitiveException");
		} catch (UnsupportedPointcutPrimitiveException ex) {
			assertEquals("Execution", PointcutPrimitive.EXECUTION, ex.getUnsupportedPrimitive());
		}
		try {
			p.parsePointcutExpression("get(* *)");
			fail("Expected UnsupportedPointcutPrimitiveException");
		} catch (UnsupportedPointcutPrimitiveException ex) {
			assertEquals("Get", PointcutPrimitive.GET, ex.getUnsupportedPrimitive());
		}
		try {
			p.parsePointcutExpression("set(* *)");
			fail("Expected UnsupportedPointcutPrimitiveException");
		} catch (UnsupportedPointcutPrimitiveException ex) {
			assertEquals("Set", PointcutPrimitive.SET, ex.getUnsupportedPrimitive());
		}
		try {
			p.parsePointcutExpression("initialization(new(..))");
			fail("Expected UnsupportedPointcutPrimitiveException");
		} catch (UnsupportedPointcutPrimitiveException ex) {
			assertEquals("Initialization", PointcutPrimitive.INITIALIZATION, ex.getUnsupportedPrimitive());
		}
		try {
			p.parsePointcutExpression("preinitialization(new(..))");
			fail("Expected UnsupportedPointcutPrimitiveException");
		} catch (UnsupportedPointcutPrimitiveException ex) {
			assertEquals("Prc-init", PointcutPrimitive.PRE_INITIALIZATION, ex.getUnsupportedPrimitive());
		}
		try {
			p.parsePointcutExpression("staticinitialization(T)");
			fail("Expected UnsupportedPointcutPrimitiveException");
		} catch (UnsupportedPointcutPrimitiveException ex) {
			assertEquals("Staticinit", PointcutPrimitive.STATIC_INITIALIZATION, ex.getUnsupportedPrimitive());
		}
	}

	public void testFormals() {
		if (needToSkip) {
			return;
		}

		PointcutParser parser = PointcutParser
				.getPointcutParserSupportingAllPrimitivesAndUsingSpecifiedClassloaderForResolution(this.getClass().getClassLoader());
		PointcutParameter param = parser.createPointcutParameter("x", String.class);
		PointcutExpression pc = parser.parsePointcutExpression("args(x)", null, new PointcutParameter[] { param });
		assertEquals("args(x)", pc.getPointcutExpression());

		try {
			pc = parser.parsePointcutExpression("args(String)", null, new PointcutParameter[] { param });
			fail("Expecting IllegalArgumentException");
		} catch (IllegalArgumentException ex) {
			assertTrue("formal unbound", ex.getMessage().contains("formal unbound"));
		}

		try {
			pc = parser.parsePointcutExpression("args(y)");
			fail("Expecting IllegalArgumentException");
		} catch (IllegalArgumentException ex) {
			assertTrue("no match for type name", ex.getMessage().contains("warning no match for this type name: y"));
		}
	}

	public void testXLintConfiguration() {
		if (needToSkip) {
			return;
		}

		PointcutParser p = PointcutParser.getPointcutParserSupportingAllPrimitivesAndUsingSpecifiedClassloaderForResolution(this
				.getClass().getClassLoader());
		try {
			p.parsePointcutExpression("this(FooBar)");
		} catch (IllegalArgumentException ex) {
			assertTrue("should have xlint:invalidAbsoluteTypeName", ex.getMessage().contains("Xlint:invalidAbsoluteTypeName"));
		}
		Properties props = new Properties();
		props.put("invalidAbsoluteTypeName", "ignore");
		p.setLintProperties(props);
		p.parsePointcutExpression("this(FooBar)");
	}

	private static class IgnoreWarningsMessageHandler implements IMessageHandler {

		public boolean handleMessage(IMessage message) throws AbortException {
			if (message.getKind() != IMessage.WARNING) {
				throw new RuntimeException("unexpected message: " + message.toString());
			}
			return true;
		}

		public boolean isIgnoring(Kind kind) {
			if (kind != IMessage.ERROR) {
				return true;
			}
			return false;
		}

		public void dontIgnore(Kind kind) {
		}

		public void ignore(Kind kind) {
		}

	}
}
